package br.edu.unifei.mestrado.commons.partition;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.graph.RelationshipWrapper;

public abstract class AbstractPartitionIndex {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	// Métodos para indexar nodes

	/**
	 * Adiciona o node ao setId
	 * 
	 * @param setId
	 * @param node
	 */
	protected abstract void addToSet(int setId, NodeWrapper node);

	/**
	 * Remove o node do setId.
	 * 
	 * @param setId
	 * @param node
	 */
	protected abstract void removeFromSet(int setId, NodeWrapper node);

	/**
	 * Busca os nodes do setId
	 * 
	 * @param key
	 * @param setId
	 * @return
	 */
	public abstract Iterable<NodeWrapper> queryNodes(String key, Integer setId);

	/**
	 * Verifica se o node existe em alguma partição.
	 * 
	 * @param idNode
	 * @return
	 */
	public abstract boolean containsKey(long idNode);

	// Métodos para indexar edges

	/**
	 * Lista de arestas que estão no corte
	 */
	private Map<Long, RelationshipWrapper> edgeCut = new HashMap<Long, RelationshipWrapper>();
	
	/**
	 * Quantidade total dos pesos das arestas do corte.
	 */
	private int edgeCutWeight = 0;

	/**
	 * Adiciona a aresta no corte e calcula o edgeCutWeight.
	 * @param edge
	 * @return
	 */
	public boolean addEdgeToCut(RelationshipWrapper edge) {
		RelationshipWrapper old = edgeCut.put(edge.getId(), edge);
		if (old == null) {
			edgeCutWeight += edge.getWeight();
			return true;
		}
		return false;
	}

	/**
	 * Remove a aresta do corte e calcula o edgeCutWeight.
	 * @param edge
	 * @return
	 */
	public boolean removeEdgeFromCut(RelationshipWrapper edge) {
		RelationshipWrapper old = edgeCut.remove(edge.getId());
		if (old != null) {
			edgeCutWeight -= edge.getWeight();
			return true;
		}
		return false;
	}

	/**
	 * Recupera o tamanho do corte.
	 * @return
	 */
	public final int getCutWeightInternal() {
		return edgeCutWeight;
	}

	/**
	 * Insere o node no indice interno e atualizada o corte
	 * 
	 * @param node
	 * @param property
	 * @param setNovo
	 */
	public final void insertToIndex(NodeWrapper node, String property, int setNovo) {
		addToSet(setNovo, node);
		updateCut(node);
	}

	/**
	 * Remove o node do indice interno e atualizada o corte
	 * 
	 * @param node
	 * @param property
	 * @param setNovo
	 */
	public final void removeFromIndex(NodeWrapper node, String property, int setNovo) {
		removeFromSet(setNovo, node);
		updateCut(node);
	}

	/**
	 * Insere o node no indice interno, e ja sabe qual a aresta que está no corte.<br>
	 * Usado pelo BKL.
	 * 
	 * @param node
	 * @param property
	 * @param setNovo
	 * @param cutRelationship
	 */
	public final void insertToIndex(NodeWrapper node, String property, int setNovo,
			RelationshipWrapper cutRelationship) {
		addToSet(setNovo, node);
		addEdgeToCut(cutRelationship);
	}

	/**
	 * Atualiza o node no indice interno
	 * 
	 * @param node
	 * @param property
	 * @param setAnterior
	 * @param setNovo
	 */
	public final void updateIndex(NodeWrapper node, String property, int setAnterior, int setNovo) {
		removeFromSet(setAnterior, node);
		addToSet(setNovo, node);
		updateCut(node);
	}

	/**
	 * Pega somente as arestas do corte.
	 * @return um Iterable com as arestas.
	 */
	public final Iterable<RelationshipWrapper> queryRelationshipsOnCut() {
		return edgeCut.values();
	}

	/**
	 * Remove todas as arestas do node e <br>
	 * adiciona somente as arestas do node que estão no corte.
	 * 
	 * @param node
	 */
	private void updateCut(NodeWrapper node) {
		logger.debug("Atualizando arestas no corte.");
		for (RelationshipWrapper edge : node.getRelationships()) {
			removeEdgeFromCut(edge);
		}

		for (RelationshipWrapper rel : node.getRelationships()) {
			if (rel.isRelationshipOnCut()) {// so adiciona se a aresta estiver no corte
				addEdgeToCut(rel);
			}
		}
	}

}
